/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

package controladoresBD;

import controladoresBD.exceptions.NonexistentEntityException;
import controladoresBD.exceptions.PreexistingEntityException;
import java.io.Serializable;
import javax.persistence.Query;
import javax.persistence.EntityNotFoundException;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import estrutura.Grupo;
import estrutura.Materias;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;

/**
 * Controlador para acesso a tabela de Matérias e suas relações.
 * @author Fábio
 */
public class MateriasJpaController implements Serializable {

    /**
     * Constrói um controlador, passando um Entity Manager Factory, que estabele
     * o acesso ao banco de dados.
     * @param emf EntityManagerFactory com a unidade de persistência já escolhida.
     */
    public MateriasJpaController(EntityManagerFactory emf) {
        this.emf = emf;
    }
    /**
     * Entity Manager Factory.
     */
    private EntityManagerFactory emf = null;

    /**
     * Cria e retorna um Entity Manager.
     * @return Retorna um Entity Manager.
     */
    public EntityManager getEntityManager() {
        return emf.createEntityManager();
    }

    /**
     * Cria uma tupla na tabela de Matérias e tuplas nas tabelas de relação, caso
     * necessário.
     * @param materias Matéria a ser criada na tabela.
     * @throws PreexistingEntityException Caso já exista uma tupla para o parâmetro.
     * @throws Exception 
     */
    public void create(Materias materias) throws PreexistingEntityException, Exception {
        if (materias.getContinencias() == null) {
            materias.setContinencias(new ArrayList<Grupo>());
        }
        if (materias.getPreReqs() == null) {
            materias.setPreReqs(new ArrayList<Grupo>());
        }
        EntityManager em = null;
        try {
            em = getEntityManager();
            em.getTransaction().begin();
            Collection<Grupo> attachedContinencias = new ArrayList<>();
            for (Grupo continenciasGrupoToAttach : materias.getContinencias()) {
                continenciasGrupoToAttach = em.getReference(continenciasGrupoToAttach.getClass(), continenciasGrupoToAttach.getId());
                attachedContinencias.add(continenciasGrupoToAttach);
            }
            materias.setContinencias(attachedContinencias);
            Collection<Grupo> attachedPreReqs = new ArrayList<>();
            for (Grupo preReqsGrupoToAttach : materias.getPreReqs()) {
                preReqsGrupoToAttach = em.getReference(preReqsGrupoToAttach.getClass(), preReqsGrupoToAttach.getId());
                attachedPreReqs.add(preReqsGrupoToAttach);
            }
            materias.setPreReqs(attachedPreReqs);
            em.persist(materias);
            for (Grupo continenciasGrupo : materias.getContinencias()) {
                continenciasGrupo.getMaterias().add(materias);
                continenciasGrupo = em.merge(continenciasGrupo);
            }
            for (Grupo preReqsGrupo : materias.getPreReqs()) {
                preReqsGrupo.getMaterias().add(materias);
                preReqsGrupo = em.merge(preReqsGrupo);
            }
            em.getTransaction().commit();
        } catch (Exception ex) {
            if (findMaterias(materias.getSigla()) != null) {
                throw new PreexistingEntityException("Materias " + materias + " already exists.", ex);
            }
            throw ex;
        } finally {
            if (em != null) {
                em.close();
            }
        }
    }

    /**
     * Edição de alguma tupla da tabela.
     * @param materias Matéria a ser editada na tabela.
     * @throws NonexistentEntityException Caso a tupla não exista na tabela.
     * @throws Exception 
     */
    public void edit(Materias materias) throws NonexistentEntityException, Exception {
        EntityManager em = null;
        try {
            em = getEntityManager();
            em.getTransaction().begin();
            Materias persistentMaterias = em.find(Materias.class, materias.getSigla());
            Collection<Grupo> continenciasOld = persistentMaterias.getContinencias();
            Collection<Grupo> continenciasNew = materias.getContinencias();
            Collection<Grupo> preReqsOld = persistentMaterias.getPreReqs();
            Collection<Grupo> preReqsNew = materias.getPreReqs();
            Collection<Grupo> attachedContinenciasNew = new ArrayList<>();
            for (Grupo continenciasNewGrupoToAttach : continenciasNew) {
                continenciasNewGrupoToAttach = em.getReference(continenciasNewGrupoToAttach.getClass(), continenciasNewGrupoToAttach.getId());
                attachedContinenciasNew.add(continenciasNewGrupoToAttach);
            }
            continenciasNew = attachedContinenciasNew;
            materias.setContinencias(continenciasNew);
            Collection<Grupo> attachedPreReqsNew = new ArrayList<>();
            for (Grupo preReqsNewGrupoToAttach : preReqsNew) {
                preReqsNewGrupoToAttach = em.getReference(preReqsNewGrupoToAttach.getClass(), preReqsNewGrupoToAttach.getId());
                attachedPreReqsNew.add(preReqsNewGrupoToAttach);
            }
            preReqsNew = attachedPreReqsNew;
            materias.setPreReqs(preReqsNew);
            materias = em.merge(materias);
            for (Grupo continenciasOldGrupo : continenciasOld) {
                if (!continenciasNew.contains(continenciasOldGrupo)) {
                    continenciasOldGrupo.getMaterias().remove(materias);
                    continenciasOldGrupo = em.merge(continenciasOldGrupo);
                }
            }
            for (Grupo continenciasNewGrupo : continenciasNew) {
                if (!continenciasOld.contains(continenciasNewGrupo)) {
                    continenciasNewGrupo.getMaterias().add(materias);
                    continenciasNewGrupo = em.merge(continenciasNewGrupo);
                }
            }
            for (Grupo preReqsOldGrupo : preReqsOld) {
                if (!preReqsNew.contains(preReqsOldGrupo)) {
                    preReqsOldGrupo.getMaterias().remove(materias);
                    preReqsOldGrupo = em.merge(preReqsOldGrupo);
                }
            }
            for (Grupo preReqsNewGrupo : preReqsNew) {
                if (!preReqsOld.contains(preReqsNewGrupo)) {
                    preReqsNewGrupo.getMaterias().add(materias);
                    preReqsNewGrupo = em.merge(preReqsNewGrupo);
                }
            }
            em.getTransaction().commit();
        } catch (Exception ex) {
            String msg = ex.getLocalizedMessage();
            if (msg == null || msg.length() == 0) {
                String id = materias.getSigla();
                if (findMaterias(id) == null) {
                    throw new NonexistentEntityException("The materias with id " + id + " no longer exists.");
                }
            }
            throw ex;
        } finally {
            if (em != null) {
                em.close();
            }
        }
    }

    /**
     * Remove uma tupla escolhida pela sua chave primária.
     * @param id Chave primária da tupla a ser removida (sigla da matéria).
     * @throws NonexistentEntityException Caso a tupla não exista na tabela.
     */
    public void destroy(String id) throws NonexistentEntityException {
        EntityManager em = null;
        try {
            em = getEntityManager();
            em.getTransaction().begin();
            Materias materias;
            try {
                materias = em.getReference(Materias.class, id);
                materias.getSigla();
            } catch (EntityNotFoundException enfe) {
                throw new NonexistentEntityException("The materias with id " + id + " no longer exists.", enfe);
            }
            Collection<Grupo> continencias = materias.getContinencias();
            for (Grupo continenciasGrupo : continencias) {
                continenciasGrupo.getMaterias().remove(materias);
                continenciasGrupo = em.merge(continenciasGrupo);
            }
            Collection<Grupo> preReqs = materias.getPreReqs();
            for (Grupo preReqsGrupo : preReqs) {
                preReqsGrupo.getMaterias().remove(materias);
                preReqsGrupo = em.merge(preReqsGrupo);
            }
            em.remove(materias);
            em.getTransaction().commit();
        } finally {
            if (em != null) {
                em.close();
            }
        }
    }

    /**
     * Procura por todas as matérias.
     * @return Retorna uma lista com todas as entidades de Matérias.
     */
    public List<Materias> findMateriasEntities() {
        return findMateriasEntities(true, -1, -1);
    }

    /**
     * Procura as matérias a partir de firstResult até maxResults
     * @param maxResults - Limitante superior para a busca.
     * @param firstResult - Limitante inferior para a busca.
     * @return Retorna uma lista com as entidades de Matérias.
     */
    public List<Materias> findMateriasEntities(int maxResults, int firstResult) {
        return findMateriasEntities(false, maxResults, firstResult);
    }

    /**
     * Procura por todas as matérias (caso o parâmetro all seja verdadeiro) ou 
     * as matérias com índices de firstResult até maxResults.
     * @param all Booleano que indica se retorna todas as matérias ou não.
     * @param maxResults Limitante superior para a busca.
     * @param firstResult Limitante inferior para a busca.
     * @return Retorna uma lista com todas as entidades buscadas.
     */
    private List<Materias> findMateriasEntities(boolean all, int maxResults, int firstResult) {
        EntityManager em = getEntityManager();
        try {
            CriteriaQuery cq = em.getCriteriaBuilder().createQuery();
            cq.select(cq.from(Materias.class));
            Query q = em.createQuery(cq);
            if (!all) {
                q.setMaxResults(maxResults);
                q.setFirstResult(firstResult);
            }
            return q.getResultList();
        } finally {
            em.close();
        }
    }

    /**
     * Procura por uma matéria em específico.
     * @param id Sigla da disciplina a ser buscada.
     * @return Retorna a entidade buscada ou null, caso ela não exista.
     */
    public Materias findMaterias(String id) {
        EntityManager em = getEntityManager();
        try {
            return em.find(Materias.class, id);
        } finally {
            em.close();
        }
    }

    /**
     * Conta a quantidade de matérias que existe no banco de dados.
     * @return Retorna a quantidade de matérias que existe no banco de dados.
     */
    public int getMateriasCount() {
        EntityManager em = getEntityManager();
        try {
            CriteriaQuery cq = em.getCriteriaBuilder().createQuery();
            Root<Materias> rt = cq.from(Materias.class);
            cq.select(em.getCriteriaBuilder().count(rt));
            Query q = em.createQuery(cq);
            return ((Long) q.getSingleResult()).intValue();
        } finally {
            em.close();
        }
    }
    
}
